package org.xbl.xchain.sdk.querier;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.googlecode.jsonrpc4j.JsonRpcHttpClient;
import lombok.Data;
import org.bouncycastle.util.encoders.Hex;
import org.xbl.xchain.sdk.SysConfig;
import org.xbl.xchain.sdk.block.BlockMetas;
import org.xbl.xchain.sdk.exception.WrongQueryParamException;
import org.xbl.xchain.sdk.block.BlockInfo;
import org.xbl.xchain.sdk.tx.TxInfo;
import org.xbl.xchain.sdk.utils.JsonRpcClientHelper;

import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.xbl.xchain.sdk.querier.RPCPath.*;

@Data
public class Querier {
    private SysConfig sysConfig;
    private Map<String, String> headers;

    public Querier(SysConfig sysConfig) {
        this.sysConfig = sysConfig;
        headers = new HashMap<>();
        headers.put("Content-Type", "text/json");
    }

    public ABCIResponse abciQuery(String path) throws WrongQueryParamException {
        return abciQuery(path, new HashMap<>());
    }

    public ABCIResponse abciQuery(String path, Map<String, Object> data) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("path", path);
        if (data != null) {
            params.put("data", Hex.toHexString(JSON.toJSONString(data).getBytes()));
        }
        ABCIResponse response = execute(ABCI_QUERY, params, ABCIResponse.class);
//        if (!response.isSuccess()) {
//            throw new WrongQueryParamException(response.getResponse().getLog());
//        }
        return response;
    }

    public ABCIResponse abciQuery(String path, byte[] data) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("path", path);
        if (data != null) {
            params.put("data", Hex.toHexString(data));
        }
        ABCIResponse response = execute(ABCI_QUERY, params, ABCIResponse.class);
        if (!response.isSuccess()) {
            throw new WrongQueryParamException(response.getResponse().getLog());
        }
        return response;
    }

    public Object query(String method) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        return execute(method, params, Object.class);

    }

    public BlockInfo queryBlock(String height) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("height", height);
        Object result = execute(BLOCK, params, Object.class);
        return JSON.parseObject(JSON.toJSONString(result), BlockInfo.class);
    }

    public BlockInfo queryBlockByHash(String blockHash) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("hash", Hex.decode(blockHash));
        Object result = execute(BLOCK_BY_HASH, params, Object.class);
        return JSON.parseObject(JSON.toJSONString(result), BlockInfo.class);
    }

    public BlockMetas queryBlock(String minHeight, String maxHeight) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("minHeight", minHeight);
        params.put("maxHeight", maxHeight);
        Object result = execute(BLOCKCHAIN, params, Object.class);
        return JSON.parseObject(JSON.toJSONString(result), BlockMetas.class);
    }

    public TxInfo queryTx(String hash) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("hash", Hex.decode(hash));
        Object result = execute(TX, params, Object.class);
        TxInfo.TxResponse txResponse = JSON.parseObject(JSON.toJSONString(result), TxInfo.TxResponse.class);
        return new TxInfo(txResponse, this.sysConfig.getVersion());
    }

    public List<TxInfo> queryTxs(Long height) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("query", "tx.height=" + height);
        Object result = execute(TX_SEARCH, params, Object.class);
        TxsResult txsResponse = JSON.parseObject(JSON.toJSONString(result), TxsResult.class);
        return txsResponse.getTxs().stream().map(txResponse -> new TxInfo(txResponse, this.sysConfig.getVersion())).collect(Collectors.toList());
    }

    public List<TxInfo> queryTxs(int page, int pageSize) throws WrongQueryParamException {
        JSONObject params = new JSONObject();
        params.put("query", "tx.height=100");
        params.put("prove", false);
        params.put("page", page);
        params.put("perPage", pageSize);
        params.put("orderBy", "desc");
        Object result = execute(TX_SEARCH, params, Object.class);
        TxsResult txsResponse = JSON.parseObject(JSON.toJSONString(result), TxsResult.class);
        return txsResponse.getTxs().stream().map(txResponse -> new TxInfo(txResponse, this.sysConfig.getVersion())).collect(Collectors.toList());
    }

    @Data
    public static class TxsResult {
        private List<TxInfo.TxResponse> txs;
        @JSONField(name = "total_count")
        private String totalCount;
    }

    public <T> T execute(String method, JSONObject params, Class<T> clazz) throws WrongQueryParamException {
        T result = null;
        try {
            JsonRpcHttpClient client = JsonRpcClientHelper.getClient(sysConfig.getRpcUrl());
            result = client.invoke(method, params, clazz);
        } catch (Throwable throwable) {
            throw new WrongQueryParamException(throwable.getMessage());
        }
        return result;
    }
}
